//: [Previous](@previous)

import Foundation
//: ## 函数的创建
func pi() -> Double {
    return 3.1414
}
pi()
//func 开头,就是函数的意思,pi是函数名,()里面放参数,没有就是空参数空传参的意思,->右边接返回值的类型


func sum(v1: Int, v2:Int) -> Int {
    return v1 + v2
}
sum(v1: 10, v2: 20)
//函数的括号()里面的参数,是一定默认只能是let,所以不用再手动写上let的前缀

//: ## 无返回值
// 3种写法
// 1.-> void
func sayHello1() -> Void {
    
}
// 2.-> ()
func sayHello2() -> () {
    
}
// 这个()其实就是空元祖的意思,(1,2)的那个元祖,现在元祖为空
// 3.直接不写右箭头
func sayHello3() {
    
}
// 隐式返回
// 如果整个函数体是一个单一表达式,那么这个函数体可以隐藏return,会隐式返回这个表达式
func sum2 (v1: Int, v2: Int) -> Int {
    v1 + v2
}
//如果这个函数体里面不止是v1+v2这一个表达式,还有别的代码,就不可以用隐身返回要写return
sum2(v1: 10, v2: 20) // 30
//: ## 返回元祖:实现多返回值
// 返回值写一个元祖,里面装多个需要的返回值组合,用于一次性要返回多个值的需求,然后可以单独拿出这个元祖里面的值出来用
// 给出v1 v2 v3,要求算出和,差,平均值
func calulate(v1: Int, v2: Int) -> (sum: Int, defference: Int, average: Int) {
    let sum = v1 + v2
    let average = sum / 2
    return (sum, v1 - v2, average)
}
calulate(v1: 10, v2: 20)
//可以用个元祖来赋值这个函数,然后就可以拿返回值出来
let result = calulate(v1: 10, v2: 20)
result.sum
result.defference
result.average
//: ## 函数的文档注释
// 注意:每个项目之间要间隔一行
/// [大标题,概括]求和
///
/// [小标题,更详细的描述]将2个整数相加
///
/// - Parameter v1: 第一个整数
/// - Parameter v2: 第二个整数
/// - Parameter v3: 我乱写的
///
/// - note: [批注]传入2个整数即可
func sum1(v1: Int, v2:Int) {v1 + v2}
//: ## 参数标签
// 在参数名前面加多一个英文,其实就是在形参(time)前面加一个标签(at),在函数体内就是通过正常形参(time)来访问外面传进来的string内容,但是在函数体外,调用参数的时候就是用标签(at)
func goToWork(at time: String){
    print("this is time:\(time)")
}
goToWork(at: "8:00")
//可以使用下划线_省略标签
func sum3(_ v1: Int, _ v2: Int) -> Int{
    return v1 + v2
}
sum3(10, 20)
//这里的参数标签是下划线_,这样子写在函数体里面是正常v1+v2,但是在函数体外面被调用函数的时候,sum3(v1:xx,v2:xx)里面的传参的v1和v2就被参数标签_直接替换了,然后加上_是可以省略,就可以直接sum(10,20)
//: ## 默认参数值
// 给参数赋个默认值,在外面被调用的时候,有默认值的参数假如没有赋值进来,就会自动读取默认值
func check(name: String = "luo", age: String) {
    print("name:\(name),age:\(age)")
}
check(name: "li", age: "10") //li,10
check(age: "20")// luo,20
//: ## 可变参数
// 在参数类型后面加3个点,代表就是可变参数.表示可以传入多个这个参数类型的值,可以理解成numbers成了数组
// 注意点1:一个函数,最多只能有一个可变参数
// 注意点2:紧跟在可变参数后面的参数,不能省略参数标签,就是说可变参数后面的形参,不可以用下划线_去修饰
func sum4(_ numbers: Int...) -> Int {
    var sum = 0
    for num in numbers {
        sum += num
    }
    return sum
}
sum4(10, 20, 30, 40) //10+20+30+40 = 100
//: ## 系统自带的print函数
// 完整的代码
//func print(
//    _ items: Any...,
//    separator: String = " ",
//    terminator: String = "\n"
//)
//items,前面加了下划线_隐藏参数,所以赋值调用的时候没有参数名.
//items,后面的Any后面加了3个点,是可变参数.所以其实print(1, 2, 3, 4)可以写很多同类型参数
//separator:间隔符,默认参数值:一个空格
//terminator:结尾符号,默认参数值:换行符号,所以每次print结尾都会默认有个换行,第二次再print的输出内容已经在第二行了
print("1", "2", "3", "4", separator: "我是间隔符", terminator: "我是结尾符") //输出: 1我是间隔符2我是间隔符3我是间隔符4我是结尾符"
print("1", "2", "3", "4", separator: "-", terminator: "")
//: ## 输入输出参数
// 可以用inout定义一个输入输出参数:可以在函数内修改外部实参的值
// 注意点:
// 1.inout参数不能是可变参数,就是(_ v1: Int...)
// 2.inout参数不能有默认值
// 3.inout参数的本质是地址传递(引用传递)
// 4.inout参数只能传入可以被多次赋值的
var waiNum = 10
//func addInout(_ num: Int) {
//    //num += 5 这样子写会报错,第一个原因是函数的参数只能是let,所以这里改动参数会报错.第二个原因是哪怕这个参数可以修改也没用,函数的传递是值类型.比如把func addInout(waiNum),这样子调用了这个方法.其实就是把10传进去,num = 10,所以改动的也是num而已,想因此改动到方法以外的vaiNum是做不到的
//}
// 这种情况就需要用到输入输出参数,inout,在参数和类型中间加上inout
func addInout(_ num: inout Int) {
    num += 5
}// 10 +5 = 15
addInout(&waiNum)// 假如函数的参数是输入输出参数的,调用的时候需要在传值前面加上&,代表这个值是输入输出参数
// 应用的需求举例.两个值需要交换.3个方式
// 方法1
func swapValues(_ v1: inout Int, _ v2: inout Int) {
    let tmp = v1
    v1 = v2
    v2 = tmp
//    print(v1,v2)
}
var v11 = 10
var v22 = 20
swapValues(&v11, &v22) //输出20,10
// 方法2,使用元祖,高端玩法互转
func swapValues1(_ v1: inout Int, _ v2: inout Int) {
    (v1, v2) = (v2, v1)
//    print(v1, v2)
}
var v111 = 10
var v222 = 20
swapValues1(&v111, &v222) //20 10
// 方法3,直接使用系统自带的
swap(&v111, &v22)
//: ## 函数重载 Function Overload
// 函数重载不是重写,不涉及到继承
// 需要同时满足以下2个条件,才能构成重载
// 1,函数名称相同
// 2,参数个数不同 || 参数类型不同 || 参数标签不同
//举例:
func sumFO(v1: Int, v2: Int) -> Int{
    v1 + v2
} // 这是初始函数
func sumFO(v1: Int, v2: Int, v3: Int) -> Int{
    v1 + v2 + v3
} // 参数个数不同
func sumFO(v1: Int, v2: Double) -> Int{
    v1 + Int(v2)
} // 参数类型不同
func sumFO(_ v1: Int, _ v2: Int) -> Int{
    v1 + v2
} // 参数标签不同
func sumFO(a: Int, b: Int) -> Int{
    a + b
} // 参数标签不同

//*1:返回值类型与函数重载无关
//func sumFO(v1: Int, v2: Int) {} 比如这个函数和初始函数对比,函数名和参数都一样,只有返回值不一样,这种情况不构成重载.因为一起调用的时候会让编译器有歧义,都是sumFO(v1: v2:),会报错
//*2:默认参数值和函数重载一起使用产生二义性时,编译器并不会报错(在C++中会报错)
//func sumFO(v1: Int, v2: Int, v3: Int = 10) -> Int { v1+v2+v3 } 这个函数因为v3已经有了默认值,所以和初始函数调用的时候会一样sumFO(v1: v2:)就行,这样子编译器会产生二义性,虽然不会报错并且会执行了初始函数那个函数,但是没必要这样子写
//*3:可变参数/省略参数类型/函数重载一起使用产生二义性时,编译器有可能报错
// 3.1 func sum(v1: Int, v2: Int) -> Int { v1+v2 }
// 3.2 func sum(_ v1: Int, _ v2: Int) -> Int { v1+v2 }
// 3.3 func sum(_ v1 : Int...) -> Int { var total = 0 return total }
// 上面3个函数调用的时候都是 sum(10, 20),此时系统有可能报错

//: ### 内敛函数 (Inline Function) (知道就行)
// 意思是:正常函数被调用时,需要开辟栈空间,在这个栈空间里面进来运算,运行结束再回收栈空间.而内敛函数会直接运行函数里面的代码,节省开辟和回收栈空间的动作
// 如果开启了编译器优化(Release模式默认会开启优化),编译器会自动将某些函数变成内敛函数
// 在Build Setting,搜索optimization
func IF() {
    print("123")
}
IF()//正常是这样子调用一个函数.
print("123")//但是假如是内敛函数,这个情况就可以直接省略所有步骤,直接一句话print("123")就结束,还写什么func IF() {...和调用IF(...
@inline(never) func inlineFn1() { //永远不会被内联(即使开启了编译器优化)
    print("")
}
@inline(__always) func inlineFn2() { //开启编译器优化后,即使代码很长,也会被内联(递归调用函数/动态派发的函数除外)
    print("")
}
//在Release模式下,编译器已经自动开启了优化,会自动决定哪些函数需要内联,因此没有必要我们手动去使用@inline
//: ## 函数类型 (Function type) (重点知识)
// 每个函数都是有类型的,函数类型由形式参数类型和返回值类型组成
func FT() {} //这个函数的参数为空,返回值也为空.所以这个类型是:() ->void 或者表达成 () ->(),左边是形式参数类型,右边是返回值类型,两部分组成了这个函数的函数类型,其实就是把这个函数转变成类型一个大类表达,比如小罗是人这个类..
func sumFT(a: Int, b: Int) -> Int {
    a + b
}// 函数类型:(Int, Int) -> Int
//作用,比如直接定义变量
var fnFT: (Int, Int) -> Int = sumFT
fnFT (2, 5)// 7
//定义了一个变量fnFT,标明了这个变量的函数类型是(Int,Int)->Int,因此可以直接赋值符合这个函数类型的sumFT.当这个函数sumFT赋值给变量fnFT后,这个变量fnFT就可以直接使用函数了.这里注意正常的函数sumFT调用时要写标签sumFT(a:,b:)的,但是变量直接调用函数的情况是不需要写标签a:b:的
func sumFTFT(v1: Int, v2: Int) -> Int {//加法
    v1 + v2
}
func difference(v1: Int, v2: Int) -> Int {//减法
    v1 - v2
}
func printResult(_ math: (Int, Int) -> Int, a: Int, b: Int){
    print("resault:\(math(a, b))")
}

//当我们有函数类型这个概念,就可以把一个函数当做参数传到别的函数去
printResult(sumFTFT, a: 10, b: 20) //10+20 = 30
printResult(difference, a: 10, b: 20)  //10-20 = -10
//比如还可以在创建一个成员变量的类型的时候就定义为函数类型
//class Person {
//    var a: Int
//    var b: () -> ()
//    //这个b,就定义成了函数类型
//}

//: ## 函数类型作为函数返回值
func next(_ input: Int) -> Int {
    input + 1
}//下一步的意思,输入n,输出n+1
func previous(_ input: Int) -> Int {
    input - 1
}//上一步的意思,输入n,输出n-1
func forward(_ forward: Bool) -> (Int)->(Int) {
    return forward ? next : previous
}//[(Int)->(Int)]代表函数作为返回值;输入true,执行next这个函数;输入false,执行previous这个函数
forward(true)(3)//第一个括号里面的参数是forward的Bool值,第二个括号的3是(Int)->(Int)中的第一个Int的传参数
forward(false)(3)
//了解概念: 返回值是函数类型的函数,叫做高阶函数(Higher-Order-Function),这个forward的函数,它的返回值是(Int)->(Int)这个函数类型,所以forward这个函数就是高阶函数
//: ## typealias 用来给类型起别名
typealias Byte = Int8 //int8是一位字节嘛,所以直接起个别名就位
typealias Short = Int16 //Int16是2位
typealias Long = Int64  //ing64是8位
//typealias(类型别名): 别名 = 类型 ,反正就是左边别名,右边类型
typealias Date = (year: Int, month: Int, day: Int)//给元祖(year: Int, month: Int, day: Int)起了个别名叫Date,后面直接用Date其实就是在用这个元祖
func testAlias(_ date: Date) {
//    func testAlias(_ date: Date) 这个代码等价于 func testAlias(_ date: (year: Int, month: Int, day: Int))
    print(date.0)
    print(date.year)
}//传参类型Date其实是元祖(year: Int, month: Int, day: Int)的别名Date
testAlias((2024, 4, 15)) //date.0和date.year都是2024

typealias IntFn = (Int, Int) -> (Int) //只要左边是别名,右边是类型,就可以起别名了
func differenceAlias(v1: Int, v2: Int) -> Int {
    v1 - v2
}
let fnAlias: IntFn = differenceAlias //声明了一个常量fnAlias,类型是IntFn这个类型别名.并且将differenceAlias函数赋值给了fnAlias,意味着常量fnAlias现在指向了differenceAlias函数,换句话其实就是fnAlias现在成为了differenceAlias函数的别名
let fnAlias1: (Int, Int) -> Int = differenceAlias //这个其实和上面的表达的意义是一样的
differenceAlias(v1: 20, v2: 10)
fnAlias(20, 10)
//总结来说,fnAlias是一个类型为IntFn(代表接受2个Int类型参数,并返回1个Int参数的函数类型)的类型别名的常量,它引用了differenceAlias函数.通过fnAlias可以像使用differenceAlias函数一样直接调用该函数
let resultAl = fnAlias(20, 10) //其实就是执行了differenceAlias.20 - 10 = 10

func setFn(_ fn: IntFn) { }// 传参需要是IntFn类型,differenceAlias符合,所以可以传入
setFn(differenceAlias)
func getFn() -> IntFn { // 返回值需要是IntFn类型,differenceAlias符合,所以可以传入
    differenceAlias
}

//按照swift标准库的定义,void其实就是空元祖(),所以返回值用->void 和 ->()是一样的,void的类型别名就是->()
//typealias void = ()

//: ## 嵌套函数(Nested Function)
// 将函数定义在函数内部
func forwardNF(_ forward: Bool) -> (Int)->(Int) {//forward:向前走;这个函数的作用是判断是否向前走
    //现在定义两个函数在内部,近供此函数内部使用,无法从外部调用.使用与只需要在函数内部进行处理
    func next(_ input: Int) -> Int {
        input + 1
    }
    func previous(_ input: Int) -> Int {
        input - 1
    }
    
    //这个函数forwardNF的返回值是(Int)->(Int)类型,next和previous函数都符合,可以直接使用在返回值
    return forward ? next : previous
}
forwardNF(true)(3) //4  其实就相当于next(3)
forwardNF(false)(3) //2











//: [Next](@next)
 
